package com.w3c.pragmatic.utils;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.w3c.pragmatic.common.exception.BusinessException;
import com.w3c.pragmatic.common.enums.AliOssFileTypeEnum;
import com.w3c.pragmatic.config.AliOssConfig;
import com.w3c.pragmatic.domain.Response;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;

/**
 * 阿里云工具包
 *
 * @author wangdong
 * @since 2025/2/24 10:42
 */
@Slf4j
@Component
public class AliOssUtil {

  @Resource private AliOssConfig aliOssConfig;
  @Resource private OSS ossClient;

  /**
   * 创建存储空间
   *
   * @param ossClient OSS连接
   * @param bucketName 存储空间
   * @return 创建捅
   */
  public static Response<String> createBucketName(OSS ossClient, String bucketName) {
    // 存储空间
    if (!ossClient.doesBucketExist(bucketName)) {
      // 创建存储空间
      Bucket bucket = ossClient.createBucket(bucketName);
      log.info("创建存储空间{}成功", bucketName);
      return Response.ok(bucket.getName());
    }
    return Response.ok(bucketName);
  }

  /**
   * 删除存储空间bucketName
   *
   * @param ossClient oss对象
   * @param bucketName 存储空间
   */
  public static Response<String> deleteBucket(OSS ossClient, String bucketName) {
    ossClient.deleteBucket(bucketName);
    log.info("删除{}Bucket成功", bucketName);
    return Response.ok("删除" + bucketName + "Bucket成功");
  }

  /**
   * 创建模拟文件夹:多级目录
   *
   * @param ossClient oss连接
   * @param bucketName 存储空间
   * @param folder 模拟文件夹名如"upload/2023/01/11/"
   * @return 文件夹名
   */
  public static Response<String> createFolder(OSS ossClient, String bucketName, String folder) {
    // 判断文件夹是否存在，不存在则创建
    if (!ossClient.doesObjectExist(bucketName, folder)) {
      // 创建文件夹
      ossClient.putObject(bucketName, folder, new ByteArrayInputStream(new byte[0]));
      log.info("创建文件夹成功");
      // 得到文件夹名
      OSSObject object = ossClient.getObject(bucketName, folder);
      return Response.ok(object.getKey());
    }
    return Response.ok(folder);
  }

  /**
   * 上传到oss
   *
   * @param fileName 文件名称
   * @param inputStream 文件流
   * @return 上传结果
   */
  public Response<String> uploadFileOss(String fileName, InputStream inputStream) {
    return uploadFileOss(
        aliOssConfig.getBucketName(), aliOssConfig.getDirectory(), fileName, inputStream);
  }

  /**
   * 上传到oss
   *
   * @param bucketName 存储空间明细
   * @param directory 上传路径
   * @param fileName 文件名称
   * @param inputStream 文件流
   * @return 上传结果
   */
  public Response<String> uploadFileOss(
      String bucketName, String directory, String fileName, InputStream inputStream) {
    // 操纵oss返回结果
    PutObjectResult putObjectResult;
    try {
      // 文件元信息（Object Meta）是对上传到OSS的文件的属性描述，分为两种：HTTP标准属性（HTTP Headers）和 User Meta（用户自定义元信息）
      // 下面是HTTP标准属性
      ObjectMetadata objectMetadata = new ObjectMetadata();
      objectMetadata.setContentLength(inputStream.available()); // 该Object大小
      objectMetadata.setCacheControl("no-cache"); // 指定该Object被下载时的网页的缓存行为
      objectMetadata.setHeader("Pragma", "no-cache");
      objectMetadata.setHeader("x-oss-object-acl", "public-read");
      objectMetadata.setContentEncoding("utf-8"); // 指定该Object被下载时的内容编码格式
      objectMetadata.setContentType(getContentType(fileName)); // 设置该object文件类型
      objectMetadata.setContentDisposition("inline;filename=" + fileName); // 指定该Object被下载时的名称
      // 调用oss api上传文件
      String absoluteLocation = directory + fileName; // 绝对路径
      putObjectResult =
          ossClient.putObject(bucketName, absoluteLocation, inputStream, objectMetadata);
      log.info("oss上传，响应结果={}", JSON.toJSONString(putObjectResult));
      // 判断异常
      if (ObjectUtil.isNull(putObjectResult) || StrUtil.isEmpty(putObjectResult.getETag()))
        throw new BusinessException("oss上传，返回结果异常");
      // 返回结果(唯一MD5数字签名: putObjectResult.getETag(); 文件上传路径: 域名+桶+绝对路径)
      // 返回什么或者返回多个可以用对象封装
      return Response.ok(aliOssConfig.getUrlPrefix()+ absoluteLocation);
    } catch (IOException e) {
      log.error("oss上传，发生文件处理异常，异常信息：", e);
    } catch (BusinessException e) {
      log.error("oss上传，发生业务处理异常，异常信息：", e);
    } catch (Exception e) {
      log.error("oss上传，发生其他未知异常，异常信息：", e);
    } finally {
      try {
        if (inputStream != null) inputStream.close(); // 关闭文件流
      } catch (IOException e) {
        log.error("oss上传，关闭文件流异常，异常信息：", e);
      }
    }
    // 非成功返回(可以在各个exception封装异常信息或者直接各个return掉)
    return Response.failed("oss上传资源异常");
  }

  /**
   * 下载文件到本地
   *
   * @param bucketName 存储空间
   * @param ossDirectory Bucket下的文件的路径名+文件名 如："upload/2023/01/11/cake.jpg"
   * @param localFilePath 下载本地文件绝对路径如"C:\Users\Administrator\Desktop\oss-download\xxx.pdf"
   */
  public Response<String> downloadFileOss(
      String bucketName, String ossDirectory, String localFilePath) {
    try {
      // 创建本地文件
      File file = new File(localFilePath);
      GetObjectRequest objectRequest = new GetObjectRequest(bucketName, ossDirectory);
      // 下载OSS文件到本地文件，若指定的本地文件存在则覆盖，否则新建
      ossClient.getObject(objectRequest, file);
      log.info("下载文件到本地成功");
      return Response.ok("下载oss成功");
    } catch (OSSException | ClientException e) {
      log.error("oss下载，发生文件下载异常，异常信息：", e);
    }
    // 非成功返回
    return Response.failed("oss下载资源异常");
  }

  /**
   * 获取上传文件对象 备注：最重要的是获取上传文件的输出流InputStream
   *
   * @param bucketName 存储空间
   * @param ossDirectory Bucket下的文件的路径名+文件名 如："upload/2023/01/11/cake.jpg"
   * @return oss对象
   */
  public Response<OSSObject> getObject(String bucketName, String ossDirectory) {
    OSSObject ossObject = null;
    try {
      ossObject = ossClient.getObject(bucketName, ossDirectory);
      long fileSize = ossObject.getObjectMetadata().getContentLength(); // 文件大小
      String ossPath = ossObject.getKey(); // 文件相对路径
      InputStream is = ossObject.getObjectContent(); // 文件输入流
      log.info(
          "success to getObject,fileSize: {}, ossPath:{}, inputStream:{}", fileSize, ossPath, is);
      // 成功返回
      return Response.ok(ossObject, "获取成功");
    } catch (OSSException | ClientException e) {
      log.error("oss获取对象，发生文件异常，异常信息：", e);
    }
    return Response.failed("oss获取资源异常");
  }

  /**
   * 根据key删除OSS服务器上的文件
   *
   * @param bucketName 存储空间
   * @param ossDirectory Bucket下的文件的路径名+文件名 如："upload/2023/01/11/cake.jpg"
   */
  public Response<String> deleteObject(String bucketName, String ossDirectory) {
    try {
      // 这里给的秘钥需要删除权限
      ossClient.deleteObject(bucketName, ossDirectory);
      return Response.ok("删除oss资源成功");
    } catch (OSSException | ClientException e) {
      log.error("oss删除，发生文件删除异常，异常信息：", e);
    }
    // 非成功返回
    return Response.failed("oss删除资源异常");
  }

  /**
   * 获取上传文件url(带有效期)
   *
   * @param bucketName 存储空间
   * @param ossDirectory Bucket下的文件的路径名+文件名 如："upload/2023/01/11/cake.jpg"
   * @return url
   */
  public Response<String> getOssUrlExpire(String bucketName, String ossDirectory) {
    // 设置URl过期时间为99年：3600L*1000*24*365*99（这里可以参数化（过期时间，过期时间单位））
    Date expiration = new Date(new Date().getTime() + 60 * 1000 * 3);
    GeneratePresignedUrlRequest generatePresignedUrlRequest =
        new GeneratePresignedUrlRequest(bucketName, ossDirectory);
    generatePresignedUrlRequest.setExpiration(expiration);
    URL url = ossClient.generatePresignedUrl(generatePresignedUrlRequest);
    return Response.ok(url.toString());
  }

  // ==================================================================================================
  // ==========================================工具方法==================================================
  // ==================================================================================================

  /**
   * 通过文件名判断并获取OSS服务文件上传时文件的contentType
   *
   * @param fileName 文件名
   * @return 文件的contentType
   */
  public static String getContentType(String fileName) {
    // 文件的后缀名
    String fileExtension = fileName.substring(fileName.lastIndexOf(StringPool.DOT));
    log.info("getContentType->fileName={},fileExtension={}", fileName, fileExtension);
    for (AliOssFileTypeEnum aliEnum : AliOssFileTypeEnum.values()) {
      if (aliEnum.getCode().equalsIgnoreCase(fileExtension)) {
        return aliEnum.getText();
      }
    }
    // 默认返回类型
    return AliOssFileTypeEnum.TXT.getText();
  }
}
